"""
__author__ = '霍格沃兹测试开发学社'
__desc__ = '更多测试开发技术探讨，请访问：https://ceshiren.com/t/topic/15860'
"""
import copy
import os

'''
完成学生管理系统的多任务Web服务器
以面向对象方式实现，使用字典表示学生信息，不需要封装学生类
实现增删改查接口，返回 json 格式数据
请求方式：
添加： http://127.0.0.1:8888/add?sid=s09&name=lucy&age=23&gender=male
修改： http://127.0.0.1:8888/change?sid=s09&name=kevin&age=23&gender=male
查询： http://127.0.0.1:8888/query?sid=s09
删除： http://127.0.0.1:8888/del?sid=s09
数据需要使用文件 db.txt 进行持久化存储，并保证数据的有效性
'''


import json
import socket
import threading

class Student():
    def __init__(self, sid, name, age, gender):
        self.sid = sid
        self.name = name
        self.age = age
        self.gender = gender
    def info(self):
        render = {'sid': self.sid, 'name': self.name, 'age': self.age, 'gender': self.gender}
        return render

all_students = []
file_path = 'students.txt'

class Utils():
    # 装饰器：每次查询学号是否存在前都先获取一下所有的学生信息
    def getStudents(fun):
        def inner(self, *args, **kwargs):
            # 获取所有信息
            global all_students
            all_students = self.readFile(file_path)
            result = fun(self, *args, **kwargs)
            return result

        return inner

    '''添加学生'''
    def addStudent(self, params):
        '编号（sid), 姓名（name), 年龄（age), 性别（gender)'
        global all_students
        sid = params.get("sid")
        if self.sid_exists(sid)[0]:
                return ("学号已存在!")
        else:
            name = params.get("name")
            age = params.get("age")
            gender = params.get("gender")
            student = Student(sid, name, age, gender)
            all_students.append(student.info())
            '添加学生到文件'
            result = self.appendFile(file_path, str(student.info()))
            if result:
                json_s = json.dumps(all_students)
                return json_s
            else:
                return "添加失败！！！"

    '''修改学生'''
    def changeStudent(self, params):
        global all_students
        sid = params.get("sid")
        if self.sid_exists(sid)[0] == False:
            return ("学号不存在!")
        else:
            name = params.get("name")
            age = params.get("age")
            gender = params.get("gender")
            student_new = Student(sid, name, age, gender).info()
            for i in all_students:
                if student_new.get("sid") == i.get("sid"):
                    all_students.remove(i)
                    all_students.append(student_new)
                    # 重新覆盖写入文件
                    result = self.overwriteFile(file_path, all_students)
                    if result:
                        json_s = json.dumps(all_students)
                        return json_s
                    else:
                        return "修改失败！！！"

    '''查询学生'''
    def queryStudent(self, params):
        '编号（sid), 姓名（name), 年龄（age), 性别（gender)'
        global all_students
        sid = params.get("sid")
        if self.sid_exists(sid)[0] == False:
            return ("学号不存在!")
        else:
            return json.dumps(self.sid_exists(sid)[1])

    '''删除学生'''
    def delStudent(self, params):
        '编号（sid), 姓名（name), 年龄（age), 性别（gender)'
        global all_students
        sid = params.get("sid")
        if self.sid_exists(sid)[0] == False:
            return ("学号不存在!")
        else:
            for i in all_students:
                if sid == i.get("sid"):
                    all_students.remove(i)
                    # 重新覆盖写入文件
                    result = self.overwriteFile(file_path, all_students)
                    if result:
                        json_s = json.dumps(all_students)
                        return json_s
                    else:
                        return "删除失败！！！"


    '''检查学号是否存在，并返回检查结果，以及学生信息'''
    @getStudents
    def sid_exists(self, sid):
        for i in all_students:
            if i.get('sid') == sid:
                return [True, i]  # 存在，且返回学生信息
        return [False, None]  # 不存在

    '''检查姓名是否存在，并返回检查结果，以及学生信息'''

    @getStudents
    def name_exists(self, name):
        result = []
        for i in all_students:
            if i.get('name') == name:
                result.append(i)
        if len(result) > 0:
            return [True, result]  # 存在，且返回学生信息
        else:
            return [False, None]  # 不存在


    # 追加写入数据
    def appendFile(self, file_path, data):
        try:
            file = open(file_path, "a")
            # 写入数据
            file.write(data)
            file.write("\n")
        except Exception:
            return False
        else:
            return True
        finally:
            # 关闭文件
            file.close()

    # 覆盖写入数据
    def overwriteFile(self, file_path, datas):
        try:
            file = open(file_path, "w")
            # 写入数据
            for i in datas:
                # 写入数据
                file.write(str(i))
                file.write("\n")
            # 关闭文件
            file.close()
        except Exception as e:
            print(e)
            return False
        else:
            return True
        finally:
            # 关闭文件
            file.close()

    # 读取数据
    def readFile(self, file_path):
        students = []
        if not os.path.exists(file_path):
            with open(file_path, mode='w', encoding='utf-8') as ff:
                print("文件创建成功!")
        else:
            with open(file_path, 'r') as f:
                # 使用循环逐行读取文件内容
                for line in f:
                    # student = line.strip() # 输出每一行，使用strip()方法去除行末尾的换行符
                    student = line
                    to_dict = eval(student)  # 将str转为dict
                    students.append(to_dict)
            # 或者可以使用 while 循环来一行一行读取文件内容
            # line = f.readline()
            # while line:
            #     print(line.strip())
            #     line = f.readline()
        return students

class Server():
    def startServer(self):
        # 创建一个socket对象
        server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # stream是流，流是二进制
        # 设置复用端口
        server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, True)
        # 绑定端口
        server.bind(("", 8888))  # 传入的是一个元祖，ip是字符串 未传时默认是本机。数字是端口
        # 启动服务器的监听
        server.listen(256)  # 256是连接数量
        print("Server start on 127.0.0.1:8888")

        while True:
            # 接收客户断的请求，返回客户端的sokcer对象，和端口
            client, ip_port = server.accept()
            print(f"客户端{ip_port[0]}使用{ip_port[1]}端口成功")
            thread = threading.Thread(target=self.handleClinetRequest, args=(client,))
            # 设置守护线程
            thread.daemon = True
            # 启动子线程去处理客户端请求
            thread.start()

    def handleClinetRequest(self, client):
        recv_data = client.recv(4064).decode('utf-8')  # 二进制解码
        if len(recv_data) == 0:
            client.close()
            return
        # 解释客户端的请求
        print(recv_data)
        request = self.parseRequest(recv_data)
        # 根据用户请求去做相应的处理，不同的请求使用不同的函数进行处理，这个处理函数称为接口，找接口的过程，称为路由
        response = self.router(request)
        # 服务器将响应数据返回给客户端
        client.send(response)
        # 服务器为客户端 提供一次服务完成，关闭连接
        client.close()

    def parseRequest(self, recv_data):
        request = {
            "method": "",
            "path": "",
            "params": {}
        }
        # 先获取到第一行请求行数据
        recv_data = recv_data.split()
        # 保存请求方法
        request["method"] = recv_data[0]
        # 处理请求路径 和参数
        path = recv_data[1]
        if "?" in path:
            tmp = path.split("?")
            # 保存路径
            path = tmp[0]
            # 提取参数
            params = tmp[1].split("&")
            for s in params:
                # 分解析查询参数字符串
                k, v = s.split("=")
                request["params"][k] = v
        # 保存请求路径
        request["path"] = path
        # 返回解析结果
        return request

    def router(self, request):
        response_body = ""
        path = request.get("path")
        params = request.get("params")
        if path == "/add":
            response_body = self.add(params)
        elif path == "/change":
            response_body = self.change(params)
        elif path == "/query":
            response_body = self.query(params)
        elif path == "/del":
            response_body = self.delete(params)
        else:
            response_body = self.other(path)
        response_body = response_body.replace('},','},<br>')

        # 拼装完整的响应报文
        response = "HTTP/1.1 200 OK\r\n"
        # image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
        response += "Content-Type: text/html;charset=utf-8\r\n"
        response += "Server: MyWebServer V1.0\r\n"
        response += "\r\n"
        # 因为使用的是TCP字节流传输数据，所以要对响应数据进行转换类型
        response = response.encode("utf-8")
        response += response_body.encode()
        return response

    # 下面这些函数称为接口函数，用来对不同的请求路径 接收相应的数据信息
    # /add
    def add(self,params):
        untils = Utils()
        return untils.addStudent(params)

    # /change
    def change(self, params):
        untils = Utils()
        return untils.changeStudent(params)

    # /query
    def query(self, params):
        untils = Utils()
        return untils.queryStudent(params)

    # /del
    def delete(self, params):
        untils = Utils()
        return untils.delStudent(params)

    def other(self, path):
        return "<h1>访问地址不存在<h1>"

if __name__ == '__main__':
    Server().startServer()